﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace ObjectModel.Excel
{
    /// <summary>
    /// An Excel range object wrapper that provides events for that range.
    /// </summary>
    public class ObservableRange : IRangeEvents
    {
        private Range mRange;
        private dynamic mPrevValue;
        private string mLastAddress;
        private WorksheetEvents mWorksheetEvents;

        public event RangeChangedEventHandler RangeChanged;
        public event EventHandler RangeSelected;

        public ObservableRange(Range range)
        {
            Range = range;
        }

        public Range Range
        {
            get { return mRange; }
            set
            {
                mRange = value;
                mPrevValue = value.Value;
                mLastAddress = value.Address;
                
                // Note: it is safe to subscribe this class to the events of a single worksheet
                // as the worksheet property of an Excel range never changes, even if the
                // physical range it originally referred to is moved to another worksheet/workbook.
                mWorksheetEvents = AppEvents.Worksheets[mRange.Worksheet];
                mWorksheetEvents.Subscribe(this);
            }
        }

        public string Address
        {
            get { return Range.Address; }
        }

        public dynamic Value
        {
            get { return Range.Value; }
            set
            {
                // Disable events while assigning value.
                mWorksheetEvents.Enabled = false;
                Range.Value = value;
                mWorksheetEvents.Enabled = true;
            }
        }

        public void Select()
        {
            mWorksheetEvents.Enabled = false;
            ((_Worksheet)mRange.Worksheet).Activate();
            mRange.Select();
            mWorksheetEvents.Enabled = true;
        }
        
        public void OnWorksheetChanged() 
        {
            string currAddress = null;

            // Check to see if range is still valid.
            try { currAddress = Range.Address; }
            catch (COMException)
            {
                RangeChangedEventArgs e = new RangeChangedEventArgs(RangeChangedType.Deleted);

                OnRangeChanged(e);
                if (!e.Undone)
                    mWorksheetEvents.Unsubscribe(this);

                return;
            }

            if (currAddress != mLastAddress)
            {
                RangeChangedEventArgs e = new RangeChangedEventArgs(RangeChangedType.Moved);

                OnRangeChanged(e);
                if (!e.Undone)
                    mLastAddress = currAddress;
            }

            if (!Equals(Range.Value, mPrevValue))
            {
                RangeChangedEventArgs e = new RangeChangedEventArgs(RangeChangedType.ValueChanged);

                OnRangeChanged(e);
                if (!e.Undone)
                    mPrevValue = Range.Value;
            }

        }

        public void OnWorksheetSelectionChange(EventArgs e)
        {
            if (RangeSelected != null)
                RangeSelected(this, e);
        }

        void OnRangeChanged(RangeChangedEventArgs e)
        {
            if (RangeChanged != null)
                RangeChanged(this, e);
        }
    }
}
